#include "file_io.h"
#include "mmgr.h"
#include "common_macros.h"
#include "log.h"

#include "num/crc32.h"	// use CRC32 for this instead?
extern "C" 
{	
	#include "zlib/zlib.h"
}

#ifdef _PLATFORM_WIN32
#include <windows.h>
#endif //_PLATFORM_WIN32
#ifdef _PLATFORM_WII
#include "wii_mmgr.h"
#endif

#ifdef _PLATFORM_ANDROID
#include "SDL2/include/SDL.h"
#endif

#include <cassert>
using namespace util;

#ifdef _PLATFORM_WII

unsigned char*		FileIO::GetTempBuffer(unsigned int Size, void* pToWrite)
{
    static unsigned char TempBuffer[MAX_TEMP_SIZE] ATTRIBUTE_ALIGN(32);
	m_pStaticBuffer =  TempBuffer;

	unsigned char*	TempA = NULL;
	if (Size < MAX_TEMP_SIZE)
	{
		TempA = m_pStaticBuffer;
	} else
	{
		TempA = (unsigned char*)MMGR_AllocEx(HEAP_MEM1, Size, 32);
	}
	if (pToWrite)
	{
		memcpy(TempA, pToWrite, Size);
	}
	return TempA;
}


void				FileIO::FreeTempBuffer(unsigned char* pData)
{
	if (pData == m_pStaticBuffer)
	{
	// nothing needed?
	} else
	{
	// we created it.. time to delete it
		UTIL_SAFE_DELETEA(pData);
	}
}

bool				FileIO::MoveToHome(const std::string& From)
{
    char homePath[NAND_MAX_PATH]="";
    NANDGetHomeDir(homePath);
    return (NAND_RESULT_OK==NANDMove(From.c_str(), homePath));
}
#endif // _PLATFORM_WII

FileIO::FileIO() :
	AbstractFile()
{
#ifdef _PLATFORM_WII
	// wii specific initialization?
	m_pStaticBuffer =  0;
#else

#ifdef _PLATFORM_ANDROID
	m_pSDLFile = NULL;
#endif

	m_pFile = NULL;
#endif //_PLATFORM_WII


}

FileIO::~FileIO()
{
	if (m_Open)
		Close();

}

bool	FileIO::Open(const std::string& Name, unsigned long	Format)
{
	m_Format = Format;

	std::string	Access = "r";
	if (Format & FILE_WRITE)		Access="w";
	if (Format & FILE_BINARY)		Access+="b";

#ifdef _PLATFORM_WII
// write only makes sense for nand.
// scream to log-file anyways?
	if (m_Format & FILE_WRITE)		m_Format = m_Format | FILE_NAND;
	if (m_Format & FILE_NAND)
	{
		u8				AccessType = 0;
		int				Result = -1;
		if (Format & FILE_READ)
		{
			if (Format & FILE_WRITE)	AccessType = NAND_ACCESS_RW;
			else						AccessType = NAND_ACCESS_READ;
		} else
		if (Format & FILE_WRITE)
		{
			AccessType = NAND_ACCESS_WRITE;
			Result = NANDCreate(Name.c_str(), NAND_PERM_OWNER_READ | NAND_PERM_OWNER_WRITE, AccessType);
		} 
		Result = NANDOpen(Name.c_str(), &m_NANDFile, AccessType);
		// get data from info?
		m_Open =  (Result == NAND_RESULT_OK);
	} else
	if (m_Format & FILE_DISC)
	{
		const char*	Filename = Name.c_str();
		memset(&m_DVDFile, 0, sizeof(m_DVDFile));
		m_Open = (DVDOpen(Filename, &m_DVDFile) == TRUE);
	}
#else

#ifdef _PLATFORM_ANDROID

	_LOG(MSG_COMMENT, "before calling SDLRWFromFile ****** " << Name );

	SDL_RWops* SDLFile = SDL_RWFromFile(Name.c_str(), Access.c_str());
	if(SDLFile)
	{
		_LOG(MSG_COMMENT, "android loading file was good ****** " << Name );
		m_pSDLFile = SDLFile;
		m_Open = true;
		return true;
	}
	else
	{
		_LOG(MSG_COMMENT, "android loading file was fail ****** " << Name );
		return false;
	}


#else


	m_pFile = fopen(Name.c_str(), Access.c_str());
#endif

	if (m_pFile)	m_Open = true;

#endif //_PLATFORM_WII
	m_Name = Name;
	return m_Open;
}

void	FileIO::Close()
{
#if  defined(_PLATFORM_WII)
	if (m_Format & FILE_NAND)
	{
		NANDClose(&m_NANDFile);
	} else
	if (m_Format & FILE_DISC)
	{
		DVDClose(&m_DVDFile);
	}
#elif defined(_PLATFORM_ANDROID)
	if(m_pSDLFile)
		m_pSDLFile->close(m_pSDLFile);
#else
	if (m_pFile)
	{
		fclose(m_pFile);
		m_pFile = NULL;
	}
#endif //_PLATFORM_WII

	m_Open = false;
}

unsigned int	FileIO::GetSize()
{
	if (!m_Open) return 0;
#if defined( _PLATFORM_WII)
	if (m_Format & FILE_NAND)
	{
		u32	Size;
		NANDGetLength(&m_NANDFile, &Size);
		return Size;
	} else
	if (m_Format & FILE_DISC)
	{
		return DVDGetLength(&m_DVDFile);
	}
#elif defined(_PLATFORM_ANDROID)
	if(m_pSDLFile)
		return (unsigned int)m_pSDLFile->size;
#else
	if (m_pFile)
	{
		fseek(m_pFile, 0, SEEK_END);
		unsigned int	Size = ftell(m_pFile);	// file size
		fseek(m_pFile, 0, SEEK_SET);
		return Size;
	} 
#endif //_PLATFORM_WII
	return 0;
}

bool	FileIO::Write(void*	pData, unsigned int		Size)
{
	if (!m_Open) return false;
#if defined(_PLATFORM_WII)

	if (m_Format & FILE_NAND)
	{
		unsigned char*	TempA = GetTempBuffer(Size, pData);
		bool	Success =  NANDWrite(&m_NANDFile, TempA, Size) == Size;
		FreeTempBuffer(TempA);
		return Success;
	} else
	if (m_Format & FILE_DISC)
	{
		// can't write to disk
		return false;
	}
#elif defined(_PLATFORM_ANDROID)

	if(m_pSDLFile)
	{
		return m_pSDLFile->write(m_pSDLFile,pData,Size,1) > 0;
	}

#else
	if (m_pFile)
	{
		fwrite(pData, Size, 1, m_pFile);
		return true;
	} 

#endif //_PLATFORM_WII
	return false;
}

bool	FileIO::WriteStr(const std::string& Data)
{	
	if (!m_Open) return false;
#if defined( _PLATFORM_WII)
	Write((void*)&Data[0], Data.size() +1);
#elif defined(_PLATFORM_ANDROID)

	if(m_pSDLFile)
	{
		m_pSDLFile->write(m_pSDLFile, (void*)&Data[0],Data.size()+1, 1 );
		return true;
	}

#else
	if (m_Format & FILE_ASCII)
	{
		fprintf(m_pFile, Data.c_str());
		return true;
	} else
	{
		Write((void*)&Data[0], Data.size() +1);
		return true;
	}
#endif // 
	return false;
}

bool	FileIO::Read(void* pData, unsigned int		Size)
{
	if (!m_Open) return false;
#if defined( _PLATFORM_WII)
	unsigned int	ReadSize =  OSRoundUp32B(Size);
	if (m_Format & FILE_NAND)
	{
		// provide a flag for optimized loading here?
	
		// note: both destination and size must be 32byte aligned.
		// do we need to handle that explicitly here?
		bool Success  = false;
		if (m_Format & FILE_MEM32)
		{
			int	BytesRead = NANDRead(&m_NANDFile, pData, ReadSize); 
			Success = BytesRead == (s32)ReadSize;
		} else
		{
			unsigned char*	TempA = GetTempBuffer(ReadSize, NULL);	
			Success = NANDRead(&m_NANDFile, TempA, ReadSize) == (s32)ReadSize;
			memcpy(pData, TempA, Size);
			FreeTempBuffer(TempA);
		}
		return Success;
	} else
	if (m_Format & FILE_DISC)
	{
		// the last number is the 'seek' pos. 
		// Q: does set hold enough data, or do we need to track that as well?
		bool Success = false;
		if (m_Format & FILE_MEM32)
		{
			int	BytesRead = DVDRead(&m_DVDFile, pData, (s32)ReadSize, 0);
			Success = BytesRead == (s32)ReadSize;
		} else
		{
			unsigned char*	TempA = GetTempBuffer(ReadSize, NULL);	
			Success = DVDRead(&m_DVDFile, TempA, (s32)ReadSize, 0) == (s32)ReadSize;
			memcpy(pData, TempA, Size);	// copy it back
			FreeTempBuffer(TempA);
		}
		return Success;
	}
#elif defined(_PLATFORM_ANDROID)

	
	if(m_pSDLFile)
	{
		m_pSDLFile->read(m_pSDLFile,pData,Size,1);
		_LOG(MSG_COMMENT, "after calling  m_pSDLFile->read(m_pSDLFile,pData,Size,1); ");
		return true;
	}



#else
	if (m_pFile)
	{
		fread(pData, Size, 1, m_pFile);
		return true;
	} 
#endif //_PLATFORM_WII
	return false;
}


void	FileIO::SetPos(unsigned int Pos)
{
	if (!m_Open) return;

#if defined( _PLATFORM_WII)
	if (m_Format & FILE_NAND)
	{
		NANDSeek(&m_NANDFile, (s32)Pos, NAND_SEEK_SET);
	} else
	if (m_Format & FILE_DISC)
	{
		DVDSeek(&m_DVDFile, (s32)Pos);
	}
#elif defined(_PLATFORM_ANDROID)

	if(m_pSDLFile)
	{
		m_pSDLFile->seek(m_pSDLFile,Pos,SEEK_SET);
	}

#else
	if (m_pFile)
	{
		fseek(m_pFile, Pos, SEEK_SET);
	} 
#endif //_PLATFORM_WII
}

bool		FileIO::CheckFreeSpace(int Size)
{
#ifdef _PLATFORM_WII
	const int	BlockSize = 16 * 1024;
	unsigned long	Blocks = (unsigned long)ceil(float(Size)/float(BlockSize));
	unsigned long	Ans = 0xffffffff;
	int	Res = NANDCheck(Blocks, 1, &Ans);
	return (Ans == 0) && (Res == NAND_RESULT_OK);
//	return NANDCreateDir(Name.c_str(), NAND_PERM_OWNER_READ | NAND_PERM_GROUP_READ | NAND_PERM_GROUP_WRITE, 0)==NAND_RESULT_OK;
#endif
	return true;
}

bool	FileIO::MakeDirectory(const std::string& Name)
{
#ifdef _PLATFORM_WII
	return NANDCreateDir(Name.c_str(), NAND_PERM_OWNER_READ | NAND_PERM_GROUP_READ | NAND_PERM_GROUP_WRITE, 0)==NAND_RESULT_OK;
#endif

#ifdef _PLATFORM_WIN32
	return ::CreateDirectory(Name.c_str(), NULL) == TRUE;
#endif //_PLATFORM_WII

	return false;
}
void	FileIO::ReadDirectory(const std::string& Path, std::vector<std::string>& Files)
{
#ifdef _PLATFORM_WII

	//NANDCreateDir(Name.c_str(), NAND_PERM_OWNER_READ | NAND_PERM_GROUP_READ | NAND_PERM_GROUP_WRITE, 0)==NAND_RESULT_OK;
#else
	//return ::CreateDirectory(Name.c_str(), NULL);
#endif //_PLATFORM_WII
}

bool	FileIO::FileExists(const std::string& Name)
{
#if defined( _PLATFORM_WII)
	s32 Test = DVDConvertPathToEntrynum(Name.c_str());
	return Test != -1;

#else
	FileIO		Test;
	if (Test.Open(Name,  FileIO::FILE_BINARY|FileIO::FILE_READ))
	{
		Test.Close();
		return true;
	}
	
	return false;
#endif //_PLATFORM_WII
}

//////////////////////////////////////////////////////////////////////////
// Memory file

MemoryFile::MemoryFile() :
	AbstractFile(),
	m_Max(0), m_Current(0),
	m_DataA(0)
{

}

MemoryFile::~MemoryFile()
{
	Close();
}

bool	MemoryFile::Open(const std::string& Name, unsigned long	Format)
{ 
	m_Name = "memory";
	m_Format = FILE_MEMORY;

	// we treat this similar to STD::VECTOR, start with some default size and grow it by (CurrentSize+NewData) *1.5 each time we exceed it
	Grow(64 * 1024);	// Start with 64K
	m_Open = true;
	return true;
}

void	MemoryFile::Close()
{
	UTIL_SAFE_DELETEA(m_DataA);
}

bool	MemoryFile::Write(void*	pData, unsigned int		Size)
{
	if (!IsOpen()) return false;
	if (!m_DataA) return false;
	if (m_Current + Size >= m_Max)
	{
		// resize it 
		Grow((unsigned int)((m_Max + Size) * 1.5));	// include Size here to avoid repeated calls in case we use this to grow LARGE files
		if (!m_DataA) return false;
	}
	assert(m_Current + Size < m_Max);	// just in case
	memcpy(&m_DataA[m_Current], pData, Size);
	m_Current+=Size;
	return true;
}

bool	MemoryFile::WriteStr(const std::string& Data)
{
	return Write((void*)Data.c_str(), Data.size());
}

bool	MemoryFile::Read(void* pData, unsigned int		Size)
{
	// what exactly does this mean for a memory file?
	return false;
}

void	MemoryFile::SetPos(unsigned int Pos)
{
	if (Pos >= m_Max) Pos = m_Max-1;
	m_Current = Pos;
}
bool	MemoryFile::PackData(unsigned int& NewSize, unsigned char* &DataA, unsigned int& Checksum)
{
	// Max size it could ever be
	unsigned long		MaxSize = (unsigned long )((m_Current*1.01)+12);
	unsigned char*		pTemp = new unsigned char[MaxSize];
	
	// setup the compression
	int result = compress2(pTemp, &MaxSize, m_DataA, m_Current, 9);	// max compression

	// MaxSize now holds the new compressed size
	NewSize = MaxSize;
	DataA = pTemp;	// don't delete me!

	Checksum = BuildChecksum(DataA, NewSize);
	return true;
}

void	MemoryFile::Grow(unsigned int ToNew)
{
	// do we shrink this if asked?
	int	Low = UTIL_LOW(ToNew, m_Max);	

	unsigned char*		NewA = new unsigned char[ToNew];
	if (m_DataA)
	{
		memset(NewA, 0, ToNew);
		memcpy(NewA, m_DataA, Low);

		// free old block
		UTIL_SAFE_DELETEA(m_DataA);
	}

	// and copy over the data
	m_DataA = NewA;
	m_Max = ToNew;
	if (m_Current >= m_Max) m_Current = m_Max-1;
}

unsigned int	MemoryFile::BuildChecksum(unsigned char* DataA, unsigned int Size)
{
	return num::CRC32::FastHash(DataA, Size);
}

